package tools;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;
import javax.swing.undo.*;
import java.util.*;
import java.beans.*;
import java.io.*;

/**
 * Title:
 * Description:
 * Copyright:    Copyright (c) 2001
 * Company:
 * @author
 * @version 1.0
 */

public class TextProcess extends AbstractProcess {

  private JTextArea textPane = new JTextArea();
//  private ResourceEditorPane editorPane = new ResourceEditorPane();

  private Caret caret;

  /**
   * Used for the selection of the configuration file, both open and save.
   */
  private JFileChooser fileChooser = new JFileChooser();

  /**
   * Contians the last <i>undone</i> edit that was performed.  This is the
   * foundation for the redo capability.
   */
  private UndoableEdit redoEdit;

  /**
   * Contains history of all edits performed.  This is the foundation for the
   * undo capability.
   */
  private Stack<UndoableEdit>     editHistory;

  private Map<String,Action> actions;
  private Action openAction = new AbstractAction(){
      public void actionPerformed( ActionEvent e ){
        openActionPerformed( e );
      } };

  private Action saveAction = new AbstractAction(){
      public void actionPerformed( ActionEvent e ){
//        System.out.println( e );
      } };
  private Action saveAsAction = new AbstractAction(){
      public void actionPerformed( ActionEvent e ){
//        System.out.println( e );
      } };
  private Action redoAction = new AbstractAction(){
      public void actionPerformed( ActionEvent e ){
        if ( redoEdit.canRedo() ){
          redoEdit.redo();
        }
        editHistory.push( redoEdit );
        redoEdit = null;
        updateUndoable();
      } };
  private Action undoAction = new AbstractAction(){
      public void actionPerformed( ActionEvent e ){
        redoEdit = (UndoableEdit)editHistory.pop();
        updateUndoable();
        if ( redoEdit.canUndo() ){
          redoEdit.undo();
        }
      } };
  private Action pasteAction = new AbstractAction(){
      public void actionPerformed( ActionEvent e ){
        textPane.paste();
      } };
  private Action cutAction = new AbstractAction(){
      public void actionPerformed( ActionEvent e ){
        textPane.cut();
      } };
  private Action copyAction = new AbstractAction(){
      public void actionPerformed( ActionEvent e ){
        textPane.copy();
      } };

  private boolean editable;

  private DocumentListener docListener;
  private Document document;

  private JLabel caretPosLabel;

  public TextProcess() {
    editHistory = new Stack<UndoableEdit>();

    Font f = new Font( "dialoginput", Font.PLAIN, 12 );

//    editorPane = new ResourceEditorPane( textPane );
    textPane.setFont( f );
    textPane.setColumns( 80 );
    textPane.setRows( 15 );
    setEditable( true );
    actions = new HashMap<String,Action>();
    docListener = new DocumentListener(){
      public void insertUpdate(  DocumentEvent e ){ docContentChanged(); }
      public void removeUpdate(  DocumentEvent e ){ docContentChanged(); }
      public void changedUpdate( DocumentEvent e ){ docContentChanged(); }
    };
    document = textPane.getDocument();
    caret = textPane.getCaret();

    caret.addChangeListener( new ChangeListener(){
      public void stateChanged(ChangeEvent e){
        boolean selection = caret.getDot() != caret.getMark();

        copyAction.setEnabled( selection );
        cutAction.setEnabled( editable && selection );
//        System.out.println( e );
      }
    } );

    textPane.addCaretListener( new CaretListener(){
      public void caretUpdate( CaretEvent e ){
        Element root = document.getDefaultRootElement();
        int dot = e.getDot();
        int line = root.getElementIndex( dot );
        int col = dot - root.getElement( line ).getStartOffset();

        caretPosLabel.setText( ( line + 1 ) + ":" + ( col + 1 ) );
      }
    } );

    document.addDocumentListener( docListener );
    document.addUndoableEditListener( new UndoableEditListener(){
      public void undoableEditHappened(UndoableEditEvent e){
        editHistory.push( e.getEdit() );
        redoEdit = null;
        updateUndoable();
      }
    } );

    undoAction.addPropertyChangeListener( new PropertyChangeListener(){
      public void propertyChange( PropertyChangeEvent e ){
        if ( e.getPropertyName().equals( "enabled" ) ){
//    System.out.println( getClass().getName() + ": " + getName() + ": " + undoAction.isEnabled() );
          boolean enabled = ( (Boolean)e.getNewValue() ).booleanValue();

          saveAction.setEnabled( enabled );
        }
      }
    } );

    determineValidActions();

    actions.put( "open", openAction );
    actions.put( "save", saveAction );
    actions.put( "saveas", saveAsAction );
    actions.put( "redo", redoAction );
    actions.put( "undo", undoAction );
    actions.put( "cut", cutAction );
    actions.put( "paste", pasteAction );
    actions.put( "copy", copyAction );

    setLayout( new BorderLayout() );
    JPanel textPanel = new JPanel( new BorderLayout() );

    JScrollPane scroller = new JScrollPane( textPane,
                                          JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
                                          JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS );

    textPanel.add( scroller, BorderLayout.CENTER );
/*    add( editorPane, BorderLayout.CENTER ); */

    JPanel caretPosPanel = new JPanel( new FlowLayout( FlowLayout.RIGHT ) );
    caretPosLabel = new JLabel( "1:1", JLabel.RIGHT );
    caretPosPanel.add( caretPosLabel );
    textPanel.add( caretPosPanel, BorderLayout.SOUTH );

    add( textPanel, BorderLayout.CENTER );

    textPane.addPropertyChangeListener( "editable", new PropertyChangeListener(){
      public void propertyChange( PropertyChangeEvent e ){
        editablePropChange( e );
      }
    } );

//    undoAction.setEnabled( false );

    String classPath = System.getProperty( "java.class.path" );
    java.util.StringTokenizer token = new java.util.StringTokenizer( classPath, File.pathSeparator );
    File pathDir = null;
    while ( token.hasMoreTokens() && pathDir == null ){
      pathDir = new File( token.nextToken() );
      if ( pathDir != null && pathDir.isDirectory() ){
        fileChooser.setCurrentDirectory( pathDir );
      } else {
        pathDir = null;
      }
    }

  }

  protected void addChoosableFileFilter( javax.swing.filechooser.FileFilter filter ){
    fileChooser.addChoosableFileFilter( filter );
  }

  protected void removeChoosableFileFilter( javax.swing.filechooser.FileFilter filter ){
    fileChooser.removeChoosableFileFilter( filter );
  }

  protected void editablePropChange( PropertyChangeEvent e ){
    boolean editable = ( (Boolean)e.getNewValue() ).booleanValue();

    determineValidActions();

    if ( ! editable ){
      if ( docListener != null ){
        document.removeDocumentListener( docListener );
      }
    } else {
      document.addDocumentListener( docListener );
    }
  }

  protected void docContentChanged( ){
    if ( isEditable() ){
      int docLength = document.getLength();
      boolean hasContent = docLength > 0;

      saveAsAction.setEnabled( hasContent );
      cutAction.setEnabled( editable && hasContent );
      copyAction.setEnabled( hasContent );
    }
  }

  public Map getActions(){
    return actions;
  }

  protected void determineValidActions(){
/*    Toolkit tk = Toolkit.getDefaultToolkit();

    java.awt.datatransfer.Clipboard clipboard = tk.getSystemClipboard();
    java.awt.datatransfer.Transferable clip =  clipboard.getContents( null );
*/
    pasteAction.setEnabled( editable );
    openAction.setEnabled( editable );
//    redoAction.setEnabled( editable );
//    undoAction.setEnabled( editable );
//    saveAction.setEnabled( editable );

    updateUndoable();
    docContentChanged();
  }

  public void setEditable( boolean e ){
    if ( editable != e ){
      editable = e;
      textPane.setEditable( e );
    }
  }

  public boolean isEditable(){
    return editable;
  }

  public void setText( String t ){
    textPane.setText( t );
    editHistory.removeAllElements();
    updateUndoable();
  }

  public String getText(){
    return textPane.getText();
  }

  /**
   * This method uses {@link #configFileChooser} to prompt the user for the
   * name of the configuration file to open.<p>
   *
   * OpenAction invokes this method (either from a menu or tool bar).
   *
   * @param evt Event which initiated this action.
   */
  public void openActionPerformed( ActionEvent evt ){
    Component c = (Component)evt.getSource();
    boolean done = false;

    do {
      int retVal = fileChooser.showOpenDialog( c );
      if ( retVal != fileChooser.APPROVE_OPTION ){
        done = true;
      } else {
        File f = fileChooser.getSelectedFile();

        if ( f.exists() ){
          retreiveFileContent( f );
          done = true;
        } else {
          int messageType = JOptionPane.ERROR_MESSAGE;
          int optionType = JOptionPane.YES_NO_OPTION;
          String title = "File does not exist";
          Object[] message = new Object[]{ "Specified file " + f.getAbsolutePath() +
                                  " does not exists." };
          Object[] option = new String[]{ "Cancel" };
          JOptionPane.showOptionDialog( c, message, title, optionType,
                                        messageType, null, option, option[0] );
        }
      }
    } while ( ! done );
  }

  /**
   * Retrieve the contents of a configuration file and place them into the
   * configuration model.
   *
   * @param configFile The file to be retrieved.
   */
  protected void retreiveFileContent( File file ){
    Reader os = null;
    CharArrayWriter caos = new CharArrayWriter();
    char buffer[] = new char[ 10240 ];

    try {
      os = new BufferedReader( new FileReader( file ) );
      int charsRead;

      while ( ( charsRead = os.read( buffer ) ) != -1 ){
        caos.write( buffer, 0, charsRead );
      }

//      model.setConfigFilename( configFile );
      textPane.setText( caos.toString() );
    } catch ( IOException ex ){
      ex.printStackTrace();
    } finally {
      if ( os != null ){
        try { os.close(); } catch ( IOException ex ){}
      }
    }
  }

  public InputStream getInputStream(){
    return new ByteArrayInputStream( getText().getBytes() );
  }

  public OutputStream getOverwriteOutputStream(){
    textPane.setText( "" );
    return new BufferedOutputStream( new TextOutputStream( textPane.getDocument() ) );
  }

  public OutputStream getAppendOutputStream(){
    return new BufferedOutputStream( new TextOutputStream( textPane.getDocument() ) );
  }

  /**
   * Determine the appropriate states of the saveAction, undoAction, and
   * redoAction.
   */
  private void updateUndoable(){
    undoAction.setEnabled( editable && editHistory != null && ! editHistory.empty() );
//    System.out.println( getClass().getName() + ": " + getName() + ": " + undoAction.isEnabled() );

//    saveAction.setEnabled( ! sourceText.isEditable() || undoAction.isEnabled() );

    redoAction.setEnabled( redoEdit != null );
  }

  private class TextOutputStream extends OutputStream {
    Document document;

    public TextOutputStream( Document doc ){
      document = doc;
    }

    public void write( int ch ){
      try {
        document.insertString( document.getLength(), String.valueOf( (char)ch ), null );
      } catch ( BadLocationException ex ){
      }
    }

    public void write(byte b[], int off, int len) throws IOException {
      try {
        document.insertString( document.getLength(), new String( b, off, len ), null );
      } catch ( BadLocationException ex ){
      }
    }
  }
}